// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rbchat.sqlite;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log;

import com.eva.epc.common.util.CommonUtils;
import com.x52im.rbchat.Const;
import com.x52im.rbchat.MyApplication;
import com.x52im.rainbowchat.im.dto.MsgType;
import com.x52im.rainbowchat.im.dto.RevokedMeta;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.logic.chat_root.model.Message.DownloadStatus;
import com.x52im.rbchat.logic.chat_root.model.Message.SendStatus;
import com.x52im.rbchat.logic.chat_root.model.Message.SendStatusSecondary;
import com.x52im.rbchat.logic.search.model.MsgDetailContentDTO;
import com.x52im.rbchat.logic.search.model.MsgSummaryContentDTO;
import com.x52im.rbchat.utils.ToolKits;

import java.util.ArrayList;

/**
 * 本系统的sqlLite数据表——一对一聊天消息(好友聊天或陌生人聊天)历史记录的辅助操作实
 * 现类（支持同一手机上切换不同的账号使用而不发生数据混乱）..
 * <p>
 * 本表中没有明确区分临时聊天消息或正式聊天消息，因为逻辑上讲同一个uid只有可能
 * 要么是正式聊天或要么是临时聊天消息
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @since 2.5
 */
public class ChatHistoryTable extends TableRoot
{
	private final static String TAG = ChatHistoryTable.class.getSimpleName();

	/** 表格字段名：自增id（主键）（默认ident列，无需插入数据）*/
	private static final String COLUMN_KEY_ID="_id";
	
	/** 表格字段名：本地数据所有者账号uid（联合主键之首要条件）*/
	private static final String COLUMN_KEY_ACOUNT$UID="_acount_uid";
	/** 表格字段名：与“我”聊天者的uid（联合主键之次要条件）*/
	public static final String COLUMN_KEY_UID="_uid";

	/** 表格字段名：消息发送者的UID（通过此字段，可以区分是“我”还是“对方”发的消息）@see {@link Message} */
	public static final String COLUMN_KEY_SENDER_ID="senderId";// add since 4.0

	/** 表格字段名：消息发送者昵称，@see {@link Message} */
	public static final String COLUMN_KEY_SENDER_DISPLAY_NAME ="senderDisplayName";
	/** 字段名：@see {@link Message}的同名列（此字段值目前仅用于UI显示，不作它用） */
	public static final String COLUMN_KEY_DATE="date";
	/** 表格字段名：@see {@link Message}的同名列 */
	public static final String COLUMN_KEY_TEXT="text";
	/** 表格字段名：@see {@link Message}的同名列 */
	public static final String COLUMN_KEY_MSG$TYPE="msgType";
	/**
     * 表格字段名：@see {@link Message#getFingerPrintOfProtocal()}。
     * 补充说明：本消息指纹码字段目前仅用于"我"发出的消息的QoS送达判断机制，因而收到的消息是不需要存储的也没有存储哦.
     * */
	public static final String COLUMN_FINGER$PRINT$OF$PROTOCAL="finger_print_of_protocal";

	public static final String COLUMN_KEY_UPDATE$TIME="_update_time";
	
	/** 存放于sqlLite数据库中的表名 */
	public static final String TABLE_NAME="chat_msg";
	
	/** 建表语句 */
	public static final String DB_CREATE="CREATE TABLE "
										+TABLE_NAME+" ( "	
										+COLUMN_KEY_ID+" INTEGER PRIMARY KEY AUTOINCREMENT,"
										+COLUMN_KEY_ACOUNT$UID+" TEXT  ,"
										+COLUMN_KEY_UID+" TEXT  ,"
										+COLUMN_KEY_SENDER_ID+" TEXT,"
										+COLUMN_KEY_SENDER_DISPLAY_NAME +" TEXT,"
										+COLUMN_KEY_DATE+" INTEGER,"
										+ COLUMN_KEY_MSG$TYPE +" TEXT,"
										+COLUMN_FINGER$PRINT$OF$PROTOCAL+" TEXT,"
//										+COLUMN_KEY_SEND$STATUS+" TEXT,"
										+COLUMN_KEY_TEXT+" TEXT,"
										// date('now')或CURRENT_TIMESTAMP 时间是以格林尼治标准时间为基准的，因此在中国使用的话会正好早8个小时，所以需要'localtime'参数
										+COLUMN_KEY_UPDATE$TIME+" TIMESTAMP default "+AlarmsHistoryTable.DEFAULT_UPDATE_TIME_FUNCTION//(datetime('now', 'localtime'))"
										+")";
	//singleton
	private static ChatHistoryTable instance;
	
	private ChatHistoryTable(Context context)
	{
		super(context);
	}
	
	public static ChatHistoryTable getInstance(Context context)
	{
		if(instance==null)
			instance=new ChatHistoryTable(context);
		return instance;		
	}
	
	/**
	 * 从本地sqlLite的表中查询所需数据.
	 * 
	 * @param acountUidOfOwner 本地数据的所有者账号，本条件是读取本地数据的先决条件，否则就窜数据了！
	 * @param condition 查询条件
	 * @return 游标结果集
	 */
	private Cursor queryHistoryImpl(String acountUidOfOwner, String condition)
	{
		return query(new String[]{
				 COLUMN_KEY_SENDER_ID
				,COLUMN_KEY_SENDER_DISPLAY_NAME
				,COLUMN_KEY_DATE
				,COLUMN_KEY_TEXT
//				,COLUMN_KEY_SEND$STATUS
				,COLUMN_FINGER$PRINT$OF$PROTOCAL
				, COLUMN_KEY_MSG$TYPE
				}, COLUMN_KEY_ACOUNT$UID+"='"+acountUidOfOwner+"'"+(condition == null?"":" and "+condition));
	}

	/**
	 * 返回历史聊天记录（目前是读取7天内的消息）.
	 * 
	 * @param acountUidOfOwner 本地数据的所有者账号，本条件是读取本地数据的先决条件，否则就窜数据了！
	 * @param uid 读取的是跟谁的聊天消息（即与“我”聊天者的uid）
	 * @return
	 */
	public ArrayList<Message> findHistory(String acountUidOfOwner, String uid)
	{
		ArrayList<Message> cpList= new ArrayList<Message>();
		
		//条件是 where _uid ='1002' and "_update_time>datetime('2014-04-19','-7 day')
		Cursor mCursor = queryHistoryImpl(acountUidOfOwner, COLUMN_KEY_UID+"='"+uid+"' and "+COLUMN_KEY_UPDATE$TIME
				// 默认只取（当前）起7天内的消息（注意这里的结果是逆序的哦）
				+">datetime('"+ToolKits.getDate()+"','-"+ Const.SQLITE_CHAT_MESSAGE_SOTRE_RANGE+" day')"
				+"  order by "
//					+COLUMN_KEY_UPDATE$TIME// 20190516注：因本字段值由datetime('now', 'localtime')函数生成，但它不能精确到毫秒，因而不适合参与精确排序
					+COLUMN_KEY_DATE
					+" desc");
		
		mCursor.moveToFirst();
		while(!mCursor.isAfterLast())
		{
			Message cp=new Message();

			cp.setSenderId(mCursor.getString(0));
			cp.setSenderDisplayName(mCursor.getString(1));
			cp.setDate(mCursor.getLong(2));
			cp.setText(mCursor.getString(3));

			cp.setFingerPrintOfProtocal(mCursor.getString(4));
			cp.setMsgType(CommonUtils.getIntValue(mCursor.getString(5), -1));
			
			// * 注意：目前此2字段没有存放到本地数据库，因为该条消息插入到数据库时，图上片发送、语音发送
			// * 还没开始呢，而此状态要等到发送结束后才准备，而目前无法即时把最新状态得到后再插入sqllite，
			// * 所以在从本地db中读取时就没法准确地得到此2个状态，所以干脆给个默认值吧，至少ui上看起来不会有问题
			cp.setSendStatus(SendStatus.beReceived);
			cp.setSendStatusSecondary(SendStatusSecondary.none);
			cp.getDownloadStatus().setStatus(DownloadStatus.NONE);
			
			cpList.add(cp);
			
			mCursor.moveToNext();
		}
		mCursor.close();
		return cpList;
	}

	public String getTableName()
	{
		return TABLE_NAME;
	}
	
	public long insertHistory(String acountUidOfOwner, String uid, Message cme)
	{
		if(cme != null && (cme.getText() == null || cme.getText() instanceof String))
		{
			return insertHistory(acountUidOfOwner
					, uid
					, cme.getSenderId()
					, cme.getSenderDisplayName()
					, cme.getDate()
					, (String)cme.getText()
					, String.valueOf(cme.getMsgType())
					, cme.getFingerPrintOfProtocal()
					);
		}
		else
		{
			Log.w(ChatHistoryTable.class.getSimpleName(), "未知的text类型："+cme.getText());
			return -1;
		}
	}
	
	/**
	 * 插入一行数据到表中.
	 * 
	 * @param acountUidOfOwner 本地数据的所有者账号，本条件是读取本地数据的先决条件，否则就窜数据了！
	 * @param uid 与“我”聊天者的uid
	 * @param senderDisplayName
	 * @param date
	 * @param text
	 * @param isComeMsg
	 * @param fingerPrintOfProtocal
	 * @return the row ID of the newly inserted row, or -1 if an error occurred
	 */
	private long insertHistory(String acountUidOfOwner
			, String uid
			, String senderId
			, String senderDisplayName
			, long date
			, String text
			, String isComeMsg
			, String fingerPrintOfProtocal
			)
	{
		ContentValues initialValues=new ContentValues();
		initialValues.put(COLUMN_KEY_ACOUNT$UID, acountUidOfOwner);
		initialValues.put(COLUMN_KEY_UID, uid);
		initialValues.put(COLUMN_KEY_SENDER_ID, senderId);
		initialValues.put(COLUMN_KEY_SENDER_DISPLAY_NAME, senderDisplayName);
		initialValues.put(COLUMN_KEY_DATE, date);
		initialValues.put(COLUMN_KEY_TEXT, text);
		initialValues.put(COLUMN_KEY_MSG$TYPE, isComeMsg);
		initialValues.put(COLUMN_FINGER$PRINT$OF$PROTOCAL, fingerPrintOfProtocal);

		return super.insert(TABLE_NAME, null, initialValues);//
	}
	
	/**
	 * 删除超出保存期限的老聊天消息.
	 * 
	 * @param acountUidOfOwner 本地数据的所有者账号，本条件是读取本地数据的先决条件，否则就窜数据了！
	 * @param uid 与“我”聊天者的uid
	 * @return 影响的行数, or -1 if an error occurred
	 */
	public long deleteOldHistory(String acountUidOfOwner, String uid)
	{
		return super.delete(TABLE_NAME
				// 删除7天前的所有聊天消息
				, COLUMN_KEY_ACOUNT$UID+"='"+acountUidOfOwner
					+"' and "+COLUMN_KEY_UID+"='"+uid
					+"' and "+COLUMN_KEY_UPDATE$TIME+"<=datetime('"+ToolKits.getDate()+"','-"+Const.SQLITE_CHAT_MESSAGE_SOTRE_RANGE+" day')");
	}

	/**
	 * 删除与某人的本地存储的所有聊天消息.
	 * 
	 * @param acountUidOfOwner 本地数据的所有者账号，本条件是读取本地数据的先决条件，否则就窜数据了！
	 * @param srcUid 与“我”聊天者的uid
	 * @return 影响的行数, or -1 if an error occurred
	 */
	public long deleteHistory(String acountUidOfOwner, String srcUid)
	{
		return super.delete(TABLE_NAME
				// 指定消息发送者的本地记录
				, COLUMN_KEY_ACOUNT$UID+"='"+acountUidOfOwner
						+"' and "+COLUMN_KEY_UID+"='"+srcUid+"'");
	}

	/**
	 * 删除指定指纹码对应的聊天消息.
	 *
	 * @param acountUidOfOwner 本地数据的所有者账号，本条件是读取本地数据的先决条件，否则就窜数据了！
	 * @param fpForMessage 被删除消息的指纹码
	 * @return 影响的行数, or -1 if an error occurred
	 */
	public long deleteHistoryWithFp(String acountUidOfOwner, String fpForMessage)
	{
		return super.delete(TABLE_NAME
				// 指定消息发送者的本地记录
				, COLUMN_KEY_ACOUNT$UID+"='"+acountUidOfOwner
						+"' and "+COLUMN_FINGER$PRINT$OF$PROTOCAL+"='"+fpForMessage+"'");
	}

	/**
	 * 消息撤回成功后，更新本地消息的数据。
	 *
	 * @param acountUidOfOwner 本地数据的所有者账号，本条件是读取本地数据的先决条件，否则就窜数据了！
	 * @param fpForMessage 被撤回消息的指纹码
	 * @param textObj 被撤回消息的新内容对象
	 * @return 影响的行数, or -1 if an error occurred
	 */
	public long updateForRevoke(String acountUidOfOwner, String fpForMessage, RevokedMeta textObj)
	{
		ContentValues updateValues=new ContentValues();
		updateValues.put(COLUMN_KEY_MSG$TYPE, String.valueOf(MsgType.TYPE_REVOKE));
		updateValues.put(COLUMN_KEY_TEXT, RevokedMeta.toJSON(textObj));

		// 指纹码列条件语句
		String fpField = COLUMN_FINGER$PRINT$OF$PROTOCAL+"='"+fpForMessage +"'";
		// 附加更新条件
		String where = COLUMN_KEY_ACOUNT$UID+"='"+acountUidOfOwner +"' and "+ fpField;

		Log.i(TAG, "【消息撤回】updateForRevoke时where="+where);
		return super.update(TABLE_NAME, updateValues, where);
	}


	//---------------------------------------------------------------------------------------- START
	//************* 以下方法用于搜索功能，涉及到较复杂的SQL语句，因而实现方法与上方的其它方法并不统一，请勿困惑哦！

	/**
	 * 搜索带有指定关键字的单聊、群聊消息（本方法的查询会将结果按聊天对象进行聚合，比如结果可
	 * 能是：某某人有多少条消息包含此结果、某群有多少条消息包含此结果，不过当结果只有一条时会显示该唯一消息内容）.
	 *
	 * @param keyword 要查询的关键字
	 * @param limit 显定返回的查询结果数（如果是大于0则起效，否则表示不限定）
	 * @return 返回查询结果，如果为空也会返回空集合对象而不是null
	 */
	public ArrayList<MsgSummaryContentDTO> searchMessagesSummery(String keyword, int limit)
	{
		ArrayList<MsgSummaryContentDTO> cpList= new ArrayList<>();

		String localUid = MyApplication.getInstance2().getIMClientManager().getLocalUserInfo().getUser_uid();
		if(!CommonUtils.isStringEmpty(keyword, true) && localUid != null) {
			String[] columns = {"chatType", "cnt", "dataId", "date", "text", "fp"};
			String table = ""
					+ "("
					+ "		SELECT 0 as chatType, count(_uid) AS cnt, _uid as dataId, date, text, finger_print_of_protocal as fp"
					+ "		FROM chat_msg "
					+ "		WHERE _acount_uid='"+localUid+"' AND msgType=0 AND text like '%" + keyword + "%' "
					+ "		GROUP BY _uid "// LIMIT "+limit  (注：sqlite中不支持union all中的子查询用limit，可能得再写一层子查询，这样就搞复杂了，为了简洁易懂，此限制可以后再视情况再考虑)

					+ "		UNION all "

					+ "		SELECT 1 as chatType, count(_gid) AS cnt, _gid as dataId, date, text, finger_print_of_protocal as fp"
					+ "		FROM groupchat_msg "
					+ "		WHERE _acount_uid='"+localUid+"' AND msgType=0 AND text like '%" + keyword + "%' "
					+ "		GROUP BY _gid "// LIMIT "+limit
					+ ")";

			//** just for debug START
			String sql = SQLiteQueryBuilder.buildQueryString(
					false, table, columns, null, null, null, "date DESC", null);
			Log.i(TAG, "本次searchAllMessagesSummery()方法内组织完成的SQL="+sql);
			//** just for debug END

			Cursor mCursor = db.getDb(false).query(table
					, columns, null, null, null, null, "date DESC");
			mCursor.moveToFirst();
			while (!mCursor.isAfterLast()) {

				MsgSummaryContentDTO cp = new MsgSummaryContentDTO();

				cp.setChatType(mCursor.getInt(0));
				cp.setResultCount(mCursor.getInt(1));
				cp.setDataId(mCursor.getString(2));
				cp.setDate(mCursor.getLong(3));
				cp.setText(mCursor.getString(4));
				cp.setFp(mCursor.getString(5));

				Log.w(TAG, "** cp.toString()="+cp);

				cpList.add(cp);
				mCursor.moveToNext();
			}
			mCursor.close();
		}else{
			Log.w(TAG, "searchAllMessagesSummery()时，无效的参数：keyword="+keyword+", localUid="+localUid);
		}

		return cpList;
	}

	/**
	 * 搜索指定聊天对象（单聊或群聊）内带有指定关键字的所有消息（本方法的查询结果不会聚合，有多少条就显示多少条）.
	 *
	 * @param searchResultChatType 搜索结果类型，见 {@link MsgSummaryContentDTO}
	 * @param uidOrGid 聊天对象的id（单聊时本参数为对方的uid、群聊时为所在群的gid）
	 * @param keyword 要查询的关键字
	 * @return 返回查询结果，如果为空也会返回空集合对象而不是null
	 */
	public ArrayList<MsgDetailContentDTO> searchMessagesDetail(int searchResultChatType, String uidOrGid, String keyword){

		ArrayList<MsgDetailContentDTO> cpList= new ArrayList<>();

		String localUid = MyApplication.getInstance2().getIMClientManager().getLocalUserInfo().getUser_uid();
		if(!CommonUtils.isStringEmpty(keyword, true) && localUid != null) {

			String[] columns = null;
			String tableName = null;
			String where = null;
			String orderBy = "date DESC";

			if(searchResultChatType == MsgSummaryContentDTO.SEARCH_RESULT_CHAT_TYPE_SINGLE) {
				columns = new String[]{"_uid", "senderId", "senderDisplayName", "date", "text" , "finger_print_of_protocal" };
				tableName = "chat_msg";
				where = "_acount_uid='"+localUid+"' and _uid='"+uidOrGid+"' AND msgType=0 AND text like '%" + keyword + "%'";
			}
			else if(searchResultChatType == MsgSummaryContentDTO.SEARCH_RESULT_CHAT_TYPE_SGROUP) {
				columns = new String[]{"_gid", "senderId", "senderDisplayName", "date", "text", "finger_print_of_protocal" };
				tableName = "groupchat_msg";
				where = "_acount_uid='"+localUid+"' and _gid='"+uidOrGid+"' AND msgType=0 AND text like '%" + keyword + "%'";
			}
			else{
				Log.w(TAG, "searchSomeoneMessages()时，无效的searchResultChatType="+searchResultChatType);
				return cpList;
			}

			//** just for debug START
			String sql = SQLiteQueryBuilder.buildQueryString(
					false, tableName, columns, where, null, null, orderBy, "100");
			Log.i(TAG, "searchSomeoneMessages()方法内组织完成的SQL="+sql);
			//** just for debug END

			Cursor mCursor = db.getDb(false).query(tableName
					, columns, where, null, null, null, orderBy, "100");
			mCursor.moveToFirst();
			while (!mCursor.isAfterLast()) {

				MsgDetailContentDTO cp = new MsgDetailContentDTO();

				cp.setChatType(searchResultChatType);
				cp.setResultCount(1);
				cp.setDataId(mCursor.getString(0));
				cp.setSenderId(mCursor.getString(1));
				cp.setSenderDisplayName(mCursor.getString(2));
				cp.setDate(mCursor.getLong(3));
				cp.setText(mCursor.getString(4));
				cp.setFp(mCursor.getString(5));

				Log.w(TAG, "** cp.toString()="+cp);

				cpList.add(cp);
				mCursor.moveToNext();
			}
			mCursor.close();
		} else {
			Log.w(TAG, "searchSomeoneMessages()时，无效的参数：keyword="+keyword+", uidOrGid="+uidOrGid+", localUid="+localUid);
		}

		return cpList;
	}
	//----------------------------------------------------------------------------------------- END
}
